﻿using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Xml;

namespace RayDen.Library.Data
{



    [Serializable]
    public struct ArrayFileHeader : ISerializableValue
    {
        public static int Size = Marshal.SizeOf(typeof(ArrayFileHeader));
        public string Sign;
        public int TypeSize;
        public string TypeName;

        public byte[] Serialize()
        {
            return (new[]
                    {
                        SerializationService.GetBytes(Sign),
                        BitConverter.GetBytes(TypeSize),
                        SerializationService.GetBytes(TypeName),
                    }).SelectMany(x => x).ToArray();
        }

        public void Deserialize(byte[] data, int offset = 0)
        {
            this.Sign = SerializationService.GetString(data, offset, sizeof(char) * 2);
            this.TypeSize = BitConverter.ToInt32(data, offset + sizeof(char) * 2);
            this.TypeName = SerializationService.GetString(data, offset + sizeof(char) * 2 + 4, (data.Length) - (offset + sizeof(char) * 2 + 4));
        }
    }

    public class ArrayFile<TElement>
        where TElement : struct, ISerializableValue
    {
        public const string ArrayFileSign = "AF";



        private TElement[] items;

        public ArrayFile(TElement[] items)
        {
            this.items = items;
        }

        public ArrayFile(string fileName)
        {
            this.items = LoadFile(fileName);
        }


        public void Load(string fileName)
        {
            this.items = LoadFile(fileName);
        }

        public void Save(string fileName)
        {
            SaveFile(this.items, fileName);
        }

        private static ArrayFileHeader CreateHeader(TElement[] items)
        {
            return new ArrayFileHeader()
                {
                    Sign = ArrayFileSign,
                    TypeSize = Marshal.SizeOf(typeof(TElement)),
                    TypeName = typeof(TElement).Name
                };
        }

        private static ArrayFileHeader LoadHeader(FileStream fs)
        {
            var data = new byte[Marshal.SizeOf(typeof(ArrayFileHeader))];
            fs.Read(data, 0, Marshal.SizeOf(typeof(ArrayFileHeader)));
            var vh = new ArrayFileHeader();
            vh.Deserialize(data);
            return vh;
        }

        private static TElement[] LoadFile(string fileName)
        {
            if (!File.Exists(fileName))
            {
                throw new ArgumentException("Invalid filename - File not exist at " + fileName);
            }

            var elementSize = Marshal.SizeOf(typeof(TElement));
            var items = new TElement[(new FileInfo(fileName).Length - ArrayFileHeader.Size) / elementSize];
            var offset = 0;
            var i = 0;
            using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                var header = LoadHeader(fs);
                if (header.Sign != ArrayFileSign)
                {
                    throw new ArgumentException("Invalid file format. Couldnt find array file sign");
                }

                if (header.TypeSize != elementSize || !typeof(TElement).Name.Equals(header.TypeName))
                {
                    throw new ArgumentException("Invalid file format. Type inside file " + header.TypeName + "is not compatible with " + typeof(TElement).Name);
                }

                while (offset < items.Length * elementSize)
                {
                    var buffer = new byte[elementSize];
                    fs.Read(buffer, offset, elementSize);
                    items[i].Deserialize(buffer);
                    offset += elementSize;
                    i++;
                }
            }
            return items;
        }

        private static void SaveFile(TElement[] data, string fileName)
        {

            var elementSize = Marshal.SizeOf(typeof(TElement));
            var offset = 0;
            var i = 0;
            using (var fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
            {
                fs.Write(CreateHeader(data).Serialize(), 0 , ArrayFileHeader.Size);
                while (offset < data.Length * elementSize)
                {

                    var buffer = data[i].Serialize();
                    fs.Write(buffer, offset, elementSize);
                    offset += elementSize;
                    i++;
                }
            }
        }
    }
}